package pers.vic.boot.generator.service;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.StringWriter;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.annotation.Resource;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import pers.vic.boot.base.model.PageInfo;
import pers.vic.boot.generator.autoconfigure.GeneratorProperties;
import pers.vic.boot.generator.dao.GeneratorDao;
import pers.vic.boot.generator.model.ColumnConfigVO;
import pers.vic.boot.generator.model.ColumnEntity;
import pers.vic.boot.generator.model.TableConfigVO;
import pers.vic.boot.generator.model.TableEntity;

/**
 * @description:
 * @author: Vic.xu
 * @date: 2020年3月11日 下午3:07:20
 */
@Service
public class GeneratorService {

	@Autowired
	GeneratorDao generatorDao;

	@Resource
	private GeneratorProperties generatorProperties;

	/**
	 * @param generatorDao
	 * @param generatorProperties
	 */
	public GeneratorService(GeneratorDao generatorDao, GeneratorProperties generatorProperties) {
		super();
		this.generatorDao = generatorDao;
		this.generatorProperties = generatorProperties;
	}

	/**
	 * 
	 */
	public GeneratorService() {
		super();
	}

	/**
	 * @description: 分页列表
	 * @author: Vic.xu
	 * @date: 2020年3月13日 下午2:39:59
	 * @param lookup
	 * @return
	 */
	public PageInfo<TableEntity> list(TableEntity lookup) {
		int total = generatorDao.countList(lookup);
		List<TableEntity> list = generatorDao.queryList(lookup);
		PageInfo<TableEntity> data = new PageInfo<TableEntity>(total, list, lookup);
		return data;
	}

	/**
	 * 构建table的详情 包含的列的信息等
	 * 
	 * @param tableName
	 * @return
	 */
	public TableEntity buildTableDetail(String tableName) {
		// 查询表信息
		TableEntity table = generatorDao.queryTable(tableName);
		if (table == null) {
			throw new RuntimeException("不存在的表信息");
		}
		// 查询列信息
		List<ColumnEntity> columns = generatorDao.queryColumns(tableName);
		// 配置信息
		boolean hasBigDecimal = false;
		String className = tableToJava(table.getTableName(), generatorProperties.getTablePrefix());

		table.setClassName(className);
		// 第一个字母小写
		table.setClassname(StringUtils.uncapitalize(className));

		// 被实体忽略的字段集合
		Set<String> ignoreColumns = generatorProperties.getIgnores();
		// 列信息
		for (ColumnEntity column : columns) {
			// 列名转换成Java属性名
			String attrName = columnToJava(column.getColumnName());
			column.setAttrName(attrName);
			column.setAttrname(StringUtils.uncapitalize(attrName));

			// 列的数据类型，转换成Java类型
			// 此处并没有默认String为默认数据类型,所以没有配置的类型在java实体中会报错
			String attrType = generatorProperties.getDataTypeConvert().getOrDefault(column.getDataType(), "unknowType");
			column.setAttrType(attrType);
			if (!hasBigDecimal && "BigDecimal".equals(attrType)) {
				hasBigDecimal = true;
			}
			if ("Date".equals(attrType)) {
				table.setHasDate(true);
			}
			// 此字段是否在实体中忽略(基类中已定义)
			if (ignoreColumns.contains(column.getColumnName())) {
				column.setEntityIgnore(true);
			}
			// 是否主键
			if ("PRI".equalsIgnoreCase(column.getColumnKey()) && table.getPk() == null) {
				table.setPk(column);
			}
			// 根据列是否是图片和日期 判断表中是否含有图片和日期

		}
		// 没主键，则第一个字段为主键
		table.setColumns(columns);
		if (table.getPk() == null) {
			table.setPk(columns.get(0));
		}
		return table;

	}

	/**
	 * 表名转换成Java类名
	 */
	private String tableToJava(String tableName, String tablePrefix) {
		if (StringUtils.isNotBlank(tablePrefix)) {
			tableName = tableName.replaceFirst(tablePrefix, "");
		}
		return columnToJava(tableName);
	}

	/**
	 * 列名转换成Java属性名 is_delete -->delete
	 */
	public static String columnToJava(String columnName) {
//		return WordUtils.capitalizeFully(columnName, new char[] { '_' }).replace("_", "");//aaBBcc-->aabbcc  不是我想要的
		return underlineToCamel(columnName);
	}

	/** 下划线 */
	static String UNDERLINE = "_";
	/** 字段属性转java属性忽略的前缀 */
	static String IGNORE_COLUMN_PREFIX = "is_";

	/**
	 * 下划线转驼峰 但是不小写
	 * 
	 * @param str
	 * @return
	 */
	public static String underlineToCamel(String str) {
		if (StringUtils.isEmpty(str)) {
			return str;
		}
		int prefixLen = IGNORE_COLUMN_PREFIX.length();
		if (str.length() > prefixLen && str.substring(0, prefixLen).equalsIgnoreCase(IGNORE_COLUMN_PREFIX)) {
			str = str.substring(prefixLen);
		}
		str = str.toLowerCase();
		StringBuffer sb = new StringBuffer();
		for (String s : str.split(UNDERLINE)) {
			sb.append(StringUtils.capitalize(s));
		}
		return sb.toString();
	}

	/**
	 * 同时导出多张表
	 */
	public byte[] exportTables(String[] tableNames, String packageName, String moduleName) {
		if (tableNames == null || tableNames.length == 0) {
			return null;
		}
		ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
		ZipOutputStream zip = new ZipOutputStream(outputStream);
		for (String tableName : tableNames) {
			generatorCode(new TableConfigVO(tableName, packageName, moduleName), zip);
		}
		IOUtils.closeQuietly(zip);
		return outputStream.toByteArray();
	}

	/**
	 * 根据条件导出
	 * 
	 * @param tableConfigVO
	 */
	public byte[] exportByConfig(TableConfigVO tableConfigVO) {
		ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
		ZipOutputStream zip = new ZipOutputStream(outputStream);
		generatorCode(tableConfigVO, zip);
		IOUtils.closeQuietly(zip);
		return outputStream.toByteArray();
	}

	private void generatorCode(TableConfigVO tableConfigVO, ZipOutputStream zip) {
		TableEntity table = buildTableDetail(tableConfigVO.getTableName());
		Map<String, Object> map = getTemplateData(table, tableConfigVO);
		VelocityContext context = new VelocityContext(map);
		// 获取模板列表
		List<String> templates = generatorProperties.getTemplates();
		for (String template : templates) {
			// 渲染模板
			StringWriter sw = new StringWriter();
			Template tpl = Velocity.getTemplate(template, "UTF-8");
			tpl.merge(context, sw);
			try {
				// 添加到zip
				String fileName = getFileName(template, table.getClassName(), generatorProperties.getPackageName(),
						generatorProperties.getModuleName());
				zip.putNextEntry(new ZipEntry(fileName));
				IOUtils.write(sw.toString(), zip, "UTF-8");
				IOUtils.closeQuietly(sw);
				zip.closeEntry();
			} catch (Exception e) {
				e.printStackTrace();
				throw new RuntimeException("渲染模板失败，表名：" + table.getTableName(), e);
			}

		}
	}

	/**
	 * 通过前端配置构建模板数据
	 * 
	 * @param tableConfigVO
	 * @return
	 */
	private Map<String, Object> getTemplateData(TableEntity table, TableConfigVO tableConfigVO) {

		String packageName = tableConfigVO.getPackageName();
		if (StringUtils.isEmpty(packageName)) {
			packageName = generatorProperties.getPackageName();
		}
		String moduleName = tableConfigVO.getModuleName();
		if (StringUtils.isEmpty(moduleName)) {
			packageName = generatorProperties.getModuleName();
		}
		// 设置velocity资源加载器
		Properties prop = new Properties();
		prop.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
		Velocity.init(prop);
		// 根据页面配置列的信息:是否列表 查询条件
		configColunm(table, tableConfigVO);
		// 封装模板数据
		Map<String, Object> map = new HashMap<String, Object>(32);
		map.put("table", table);
		map.put("tableName", table.getTableName());
		map.put("comments", table.getComments());
		map.put("pk", table.getPk());
		map.put("className", table.getClassName());
		map.put("classname", table.getClassname());
		map.put("pathName", table.getClassname().toLowerCase());
		map.put("columns", table.getColumns());
		map.put("hasBigDecimal", table.getHasBigDecimal());
		map.put("package", packageName);
		map.put("moduleName", moduleName);
		map.put("author", generatorProperties.getAuthor());
		map.put("email", generatorProperties.getEmail());
		map.put("datetime", DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm"));
		return map;
	}

	/**
	 * 获取文件名
	 */
	public static String getFileName(String template, String className, String packageName, String moduleName) {
//		String packagePath = "generator" + File.separator + className;
		String packagePath = "generator";
		String entityName = "Entity.java";
		// 把完整模板路径转化为文件名 generator/templates/Entity.java.vm --> Entity.java
		String filename = template.substring(0, template.length() - 3).substring(template.lastIndexOf("/") + 1);
		// 实体不加后缀
		String type = filename.substring(0, filename.indexOf("."));
		if (entityName.equals(filename)) {
			filename = ".java";
		}
		//形如:packagePath/Entity/xxx.java
		filename = packagePath + File.separator + type + File.separator
				+ className + filename;
		return filename;
	}

	public static void main(String[] args) {
		// 把完整模板路径转化为文件名 generator/templates/Entity.java.vm --> Entity.java
		String template = "generator/templates/Entity.java.vm";
		String filename = template.substring(0, template.length() - 3).substring(template.lastIndexOf("/") + 1);
		System.out.println(filename.substring(0, filename.indexOf(".")));

	}

	/**
	 * 配置表的列信息
	 * 
	 * @param table
	 * @param tableConfigVO
	 */
	private void configColunm(TableEntity table, TableConfigVO tableConfigVO) {
		if (table == null || tableConfigVO == null || table.getColumns() == null) {
			return;
		}
		// 表的全部列
		List<ColumnEntity> columns = table.getColumns();
		
		//被忽略的字段
		Set<String> ignoreColumns = generatorProperties.getIgnores();
		// 前端配置的需要展示的列表
		List<ColumnConfigVO> columnsConfig = tableConfigVO.getColumns();
		// 列配置转为 列名->配置
		Map<String, ColumnConfigVO> columnsConfigMap = new HashMap<String, ColumnConfigVO>(32);

		// 当由前端配置表展示的时候
		if (columnsConfig != null) {
			for (ColumnConfigVO co : columnsConfig) {
				columnsConfigMap.put(co.getColumnName(), co);
			}
			for (ColumnEntity c : columns) {
				ColumnConfigVO config = columnsConfigMap.get(c.getColumnName());
				if (config != null) {
					c.getExtend().setShow(config.getShow()).setWhere(config.getShow())
							.setCondition(config.getCondition());
				}
				if(ignoreColumns.contains(c.getColumnName())) {
					//忽略的字段默认展示
					c.getExtend().setShow(true);
				}
			}
			// 当前端没有配置的时候:默认只过滤掉大文本类型的字段
		} else {
			// 大文本类型
			Set<String> textTypes = generatorProperties.getTextTypes();
			
			columns.forEach(c -> {
				String dateType = c.getDataType();
				// 非大文本  非忽略的字段才展示
				boolean show = !textTypes.contains(dateType) && !ignoreColumns.contains(c.getColumnName());
				// 比较方式0:=; 1:like;
				int condition = "String".equalsIgnoreCase(c.getAttrType()) ? 1 : 0;
				c.getExtend().setShow(show).setWhere(show).setCondition(condition);
			});
		}

	}
	
}
